/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package edu.isi.pegasus.planner.client;

import org.griphyn.vdl.toolkit.*;
import java.io.*;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;

import edu.isi.pegasus.common.util.Version;
import edu.isi.pegasus.common.util.Currently;
import edu.isi.pegasus.planner.parser.InvocationParser;

import org.griphyn.vdl.dbschema.*;
import org.griphyn.vdl.util.Logging;
import org.griphyn.vdl.util.ChimeraProperties;
import org.griphyn.vdl.directive.*;
import gnu.getopt.*;

/**
 * This class gets the exit code of a job from invocation record.
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 2587 $
 *
 * @see org.griphyn.vdl.parser.InvocationParser
 * @see org.griphyn.vdl.dbschema.DatabaseSchema
 */
public class ExitCode extends Toolkit 
{
  /**
   * Just a string to denote the short usage.
   */
  public static final String m_usage1 =
    "[-d dbprefix | -n | -N] [-e] [-f] [-i] [-v] [-l tag -m ISO] file [..]";

  /**
   * ctor: Constructs a new instance object with the given application name.
   */
  public ExitCode( String appName )
  {
    super(appName);
  }

  /**
   * Implements printing the usage string onto stdout.
   */
  public void showUsage()
  {
    String linefeed = System.getProperty( "line.separator", "\r\n" );

    System.out.println( 
"$Id: ExitCode.java 2587 2010-10-13 22:42:35Z vahi $" + linefeed +
"VDS version " + Version.instance().toString() + linefeed );

    System.out.println( "Usage: " + this.m_application + ' ' + m_usage1 );
    System.out.println( linefeed + 
"Generic options: " + linefeed + 
" -d|--dbase dbx  associates the dbname with the database, unused." + linefeed +
" -V|--version    print version information and exit." + linefeed +
" -v|--verbose    verbose mode, switches on property vds.log.app on stdout." + linefeed +
" -i|--ignore     ignore exit code for failure, just add to database." + linefeed +
" -n|--noadd      extract the exit code, don\'t add to any database." + linefeed +
" -N|--nofail     extract and add, but do not fail on db errors." + linefeed + 
" -e|--emptyfail  takes empty files to mean failure instead of ok." + linefeed +
" -f|--fail       stops parsing on the first error found instead of going on." + linefeed +
" -l|--label tag  attaches the tag string (32 char max) to all records." + linefeed +
" -m|--mtime ISO  uses the ISO 8601 timestamp as WF mtime for all records." + linefeed +
"                 Options -l and -m must be used in conjunction!" + linefeed +
linefeed +
"The following exit codes are returned (except in -i mode):" + linefeed +
"  0  remote application ran to conclusion with exit code zero." + linefeed +
"  1  remote application concluded with a non-zero exit code." + linefeed +
"  2  kickstart failed to start the remote application." + linefeed +
"  3  remote application died on a signal, check database." + linefeed +
"  4  remote application was suspended, should not happen." + linefeed +
"  5  invocation record has an invalid state, unable to parse." + linefeed + 
"  6  illegal state, inform vds-support@griphyn.org." + linefeed +
"  7  illegal state, stumbled over an exception, try --verbose for details" + linefeed +
"  8  multiple 0..5 failures during parsing of multiple records" + linefeed );
  }

  /**
   * Creates a set of options.
   */
  protected LongOpt[] generateValidOptions()
  {
    LongOpt[] lo = new LongOpt[11];

     lo[0] = new LongOpt( "help", LongOpt.NO_ARGUMENT, null, 'h' );
     lo[1] = new LongOpt( "dbase", LongOpt.REQUIRED_ARGUMENT, null, 'd' );
     lo[2] = new LongOpt( "version", LongOpt.NO_ARGUMENT, null, 'V' );
     lo[3] = new LongOpt( "verbose", LongOpt.NO_ARGUMENT, null, 'v' );

     lo[4] = new LongOpt( "ignore", LongOpt.NO_ARGUMENT, null, 'i' );
     lo[5] = new LongOpt( "noadd", LongOpt.NO_ARGUMENT, null, 'n' );
     lo[6] = new LongOpt( "nofail", LongOpt.NO_ARGUMENT, null, 'N' );

     lo[7] = new LongOpt( "emptyfail", LongOpt.NO_ARGUMENT, null, 'e' );
     lo[8] = new LongOpt( "fail", LongOpt.NO_ARGUMENT, null, 'f' );
     lo[9] = new LongOpt( "label", LongOpt.REQUIRED_ARGUMENT, null, 'l' );
    lo[10] = new LongOpt( "mtime", LongOpt.REQUIRED_ARGUMENT, null, 'm' );

    return lo;
  }

  public void showThreads()
  {
    ThreadGroup tg = Thread.currentThread().getThreadGroup();
    while ( tg.getParent() != null ) tg = tg.getParent();
    tg.list();
  }

  public static void main(String[] args) 
  {
    int result = 0;
    int verbose = 0;
    ExitCode me = null;
    boolean noDBase = false;
    boolean ignoreDBFail = false;

    boolean failOver = true;
    boolean emptyFail = false;
    boolean earlyFail = false;
    ParseKickstart pks = null;

    String wf_label = null;
    Date wf_mtime = null;
    int wf_flag = 0;

    try {
      me = new ExitCode("exitcode");
      if ( args.length == 0 ) {
	me.showUsage();
	return ;
      }

      // get the commandline options
      Getopt opts = new Getopt( me.m_application, args, 
				"d:hefil:m:nNvV",
				me.generateValidOptions() );
      opts.setOpterr(false);
      int option = 0;
      while ( (option = opts.getopt()) != -1 ) {
	switch ( option ) {
	case 'V':
	  System.out.println( "$Id: ExitCode.java 2587 2010-10-13 22:42:35Z vahi $" );
	  System.out.println( "VDS version " + Version.instance().toString() );
	  return;

	case 'd':
	  // currently inactive option
	  opts.getOptarg();
	  break;

	case 'e':
	  emptyFail = true;
	  break;

	case 'f':
	  earlyFail = true;
	  break;

	case 'i':
	  failOver = false;
	  break;

	case 'l':
	  if ( (wf_label = opts.getOptarg()) != null ) {
	    if ( wf_label.length() > 0 ) wf_flag |= 1;
	    else wf_label = null;
	  }
	  break;

	case 'm':
	  if ( (wf_mtime = Currently.parse( opts.getOptarg() )) != null ) 
	    wf_flag |= 2;
	  break;

	case 'n':
	  noDBase = true;
	  break;

	case 'N': 
	  ignoreDBFail = true;
	  break;

	case 'v':
	  verbose = me.increaseVerbosity();
	  break;

	case '?':
	  System.out.println( "Invalid option '" + (char) opts.getOptopt() + "'" );
	default:
	case 'h':
	  me.showUsage();
	  return;
	}
      }

      // print usage information
      String arg0 = null;
      if ( opts.getOptind() >= args.length ) {
	System.out.println( "missing necessary file argument" );
	me.showUsage();
	return ;
      }

      // check for -m and -l 
      if ( wf_flag != 0 && wf_flag != 3 ) {
	me.m_logger.log( "default", 0, 
			 "Warning: Options -m and -l should be used together!" );
      }
      if ( wf_label != null && wf_label.length() > 32 ) {
	wf_label = wf_label.substring( 0, 32 );
	me.m_logger.log( "default", 0, "Warning: Truncating workflow label to \"" + 
			 wf_label + "\"" );
      }

      ChimeraProperties props = ChimeraProperties.instance();
      DatabaseSchema dbschema = null;
      String ptcSchemaName = props.getPTCSchemaName();
      if ( ptcSchemaName == null ) noDBase = true;
      if ( ! noDBase ) {
	try {
	  Connect connect = new Connect();
	  dbschema = connect.connectDatabase(ptcSchemaName);
	} catch ( Exception e ) {
	  if ( ignoreDBFail ) {
	    // if dbase errors are not fatal, just record the fact
	    String cls = e.getClass().getName();
	    String msg = e.getMessage();
	    if ( msg == null ) {
	      Throwable t = e.getCause();
	      if ( t != null ) {
		cls = t.getClass().getName();
		msg = t.getMessage();
	      }
	    }
	    me.m_logger.log( "default", 0, "While connecting to dbase: " + 
			     cls + ": " + msg + ", ignoring" );
	    dbschema = null;
	  } else {
	    // re-throw, if dbase errors are fatal (default)
	    throw e;
	  }
	}

	// check for invocation record support
	if ( dbschema == null || ! (dbschema instanceof PTC) ) {
	  me.m_logger.log( "default", 0, 
			   "Your database cannot store invocation records" + 
			   ", assuming -n mode" );
	  noDBase = true;
	}
      }

      // instantiate parser
      pks = new ParseKickstart( dbschema, emptyFail );
      pks.setNoDBase( noDBase );
      pks.setIgnoreDBFail( ignoreDBFail );
      pks.setWorkflowLabel( wf_label ); // null ok
      pks.setWorkflowTimestamp( wf_mtime ); // null ok
      dbschema = null; // decrease reference counter

      // for all files specified
      for ( int i=opts.getOptind(); i < args.length; ++i ) {
	List l = pks.parseFile( args[i] );
      
	// determine result code
	if ( failOver ) {
	  for ( Iterator j=l.iterator(); j.hasNext(); ) {
	    int status = ((Integer) j.next()).intValue();
	    me.m_logger.log( "app", 1, "exit status = " + status );
	    if ( status != 0 ) result = ( result==0 ? status : 8 );
	  }
	}

	if ( result != 0 && earlyFail ) break;
      } // for
    } catch ( FriendlyNudge fn ) {
      me.m_logger.log( "default", 0, fn.getMessage() );
      if ( failOver ) result = fn.getResult();

    } catch ( Exception e ) {
      String cls = e.getClass().getName();
      String msg = e.getMessage();
      if ( msg == null ) {
	// another try
	Throwable t = e.getCause();
	if ( t != null ) { 
	  msg = t.getMessage();
	  cls = t.getClass().getName();
	}
      }

      if ( verbose > 0 ) e.printStackTrace();
      System.err.println(cls + ": " + msg);
      result = 7;

    } finally {
      try {
	if ( pks != null ) pks.close();
      } catch ( Exception e ) {
	me.m_logger.log( "default", 0, "ERROR: " + e.getMessage() );
      }
    }

    // Java will return with 0 unless exit is used. Unfortunately, using
    // System.exit sometimes has some unwanted side-effects on d'tors,
    // thus avoid using it unless strictly necessary. 
    // me.showThreads();
    if ( result != 0 ) System.exit(result);
  }
}
